cmake_minimum_required(VERSION 3.12)

project(picotool)

if (NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release)
endif()

if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
    set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
    message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (NOT PICO_SDK_PATH)
    message(FATAL_ERROR "PICO_SDK_PATH is not defined")
endif()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
    message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
include(${PICO_SDK_PATH}/pico_sdk_version.cmake)

if (PICO_SDK_VERSION_STRING VERSION_LESS "2.1.0")
    message(FATAL_ERROR "Raspberry Pi Pico SDK version 2.1.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()

# Set PICOTOOL_CODE_OTP to compile OTP definitions in - otherwise, they are included from JSON
if (NOT PICOTOOL_CODE_OTP)
    set(PICOTOOL_CODE_OTP 0)
endif()

# allow installing to flat dir
include(GNUInstallDirs)
if (PICOTOOL_FLAT_INSTALL)
    set(INSTALL_CONFIGDIR picotool)
    set(INSTALL_DATADIR picotool)
    set(INSTALL_BINDIR picotool)
else()
    set(INSTALL_CONFIGDIR lib/cmake/picotool)
    set(INSTALL_DATADIR ${CMAKE_INSTALL_DATADIR}/picotool)
    set(INSTALL_BINDIR ${CMAKE_INSTALL_BINDIR})
endif()

# todo better install paths for this
set(DATA_LOCS "./" "${CMAKE_INSTALL_PREFIX}/${INSTALL_DATADIR}/")
string(REGEX REPLACE ";" "\",\"" DATA_LOCS_VEC "${DATA_LOCS}")
configure_file(data_locs.template.cpp ${CMAKE_CURRENT_BINARY_DIR}/data_locs.cpp)


include(ExternalProject)

if (MSVC)
    set(CMAKE_CXX_STANDARD 20)
else()
    set(CMAKE_CXX_STANDARD 14)
endif()

list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)

add_subdirectory(lib)

if (NOT DEFINED USE_PRECOMPILED)
    set(USE_PRECOMPILED true)
endif()


function(add_embedded_data_project TARGET)
    cmake_parse_arguments(PARSE_ARGV 1 OPTS "" "PREFIX;SOURCE_DIR;BINARY_DIR" "CMAKE_ARGS")

    if (USE_PRECOMPILED)
        ExternalProject_Add(${TARGET}
            PREFIX ${OPTS_PREFIX}
            SOURCE_DIR ${OPTS_SOURCE_DIR}
            BINARY_DIR ${OPTS_BINARY_DIR}
            CMAKE_ARGS ${OPTS_CMAKE_ARGS}
            INSTALL_COMMAND ""
            BUILD_ALWAYS 1
        )
    else()
        ExternalProject_Add(${TARGET}
            PREFIX ${OPTS_PREFIX}
            SOURCE_DIR ${OPTS_SOURCE_DIR}
            BINARY_DIR ${OPTS_BINARY_DIR}
            CMAKE_ARGS ${OPTS_CMAKE_ARGS}
            BUILD_ALWAYS 1
        )
    endif()
endfunction()


# compile enc_bootloader.elf
add_embedded_data_project(enc_bootloader
        PREFIX enc_bootloader
        SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/enc_bootloader
        BINARY_DIR ${CMAKE_BINARY_DIR}/enc_bootloader
        CMAKE_ARGS 
            "-DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM}"
            "-DPICO_SDK_PATH:FILEPATH=${PICO_SDK_PATH}"
            "-DUSE_PRECOMPILED:BOOL=${USE_PRECOMPILED}"
            "-DUSE_MBEDTLS=0"
            "-DPICO_DEBUG_INFO_IN_RELEASE=OFF"
        )

set(ENC_BOOTLOADER_ELF ${CMAKE_BINARY_DIR}/enc_bootloader/enc_bootloader.elf)

if (TARGET mbedtls)
    add_embedded_data_project(enc_bootloader_mbedtls
            PREFIX enc_bootloader_mbedtls
            SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/enc_bootloader
            BINARY_DIR ${CMAKE_BINARY_DIR}/enc_bootloader_mbedtls
            CMAKE_ARGS 
                "-DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM}"
                "-DPICO_SDK_PATH:FILEPATH=${PICO_SDK_PATH}"
                "-DUSE_PRECOMPILED:BOOL=${USE_PRECOMPILED}"
                "-DUSE_MBEDTLS=1"
                "-DPICO_DEBUG_INFO_IN_RELEASE=OFF"
            )

    set(ENC_BOOTLOADER_MBEDTLS_ELF ${CMAKE_BINARY_DIR}/enc_bootloader_mbedtls/enc_bootloader.elf)
endif()

if (NOT PICOTOOL_NO_LIBUSB)
    # compile xip_ram_perms.elf
    add_embedded_data_project(xip_ram_perms
            PREFIX xip_ram_perms
            SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/xip_ram_perms
            BINARY_DIR ${CMAKE_BINARY_DIR}/xip_ram_perms
            CMAKE_ARGS 
                "-DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM}"
                "-DPICO_SDK_PATH:FILEPATH=${PICO_SDK_PATH}"
                "-DUSE_PRECOMPILED:BOOL=${USE_PRECOMPILED}"
                "-DPICO_DEBUG_INFO_IN_RELEASE=OFF"
            )

    set(XIP_RAM_PERMS_ELF ${CMAKE_BINARY_DIR}/xip_ram_perms/xip_ram_perms.elf)

    # compile flash_id
    add_embedded_data_project(flash_id
            PREFIX picoboot_flash_id
            SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/picoboot_flash_id
            BINARY_DIR ${CMAKE_BINARY_DIR}/picoboot_flash_id
            CMAKE_ARGS 
                "-DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM}"
                "-DPICO_SDK_PATH:FILEPATH=${PICO_SDK_PATH}"
                "-DUSE_PRECOMPILED:BOOL=${USE_PRECOMPILED}"
                "-DPICO_DEBUG_INFO_IN_RELEASE=OFF"
            )

    set(FLASH_ID_BIN ${CMAKE_BINARY_DIR}/picoboot_flash_id/flash_id.bin)

    # We want to generate headers from WELCOME.HTM etc.
    ExternalProject_Add(otp_header_parser
            PREFIX otp_header_parser
            SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/otp_header_parser
            BINARY_DIR ${CMAKE_BINARY_DIR}/otp_header_parser
            CMAKE_ARGS "-DCODE_OTP=${PICOTOOL_CODE_OTP}"
            BUILD_ALWAYS 1 # todo remove this
            DOWNLOAD_COMMAND ""
            INSTALL_COMMAND ""
            )

    add_executable(otp_header_parse IMPORTED)
    # think this is the best way to do this - this should work in MSVC now, and possibly Xcode but that's untested
    add_dependencies(otp_header_parse otp_header_parser)
    get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
    if (is_multi_config)
        # use the first config
        list(GET CMAKE_CONFIGURATION_TYPES 0 tmp_config)
        set_property(TARGET otp_header_parse PROPERTY IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/otp_header_parser/${tmp_config}/otp_header_parse)
    else()
        set_property(TARGET otp_header_parse PROPERTY IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/otp_header_parser/otp_header_parse)
    endif()

    if (PICOTOOL_CODE_OTP)
        set(GENERATED_H ${CMAKE_CURRENT_BINARY_DIR}/otp_contents.h)
        add_custom_target(generate_otp_header DEPENDS ${GENERATED_H})
        add_custom_command(OUTPUT ${GENERATED_H}
                COMMENT "Generating ${GENERATED_H}"
                DEPENDS ${PICO_SDK_PATH}/src/rp2350/hardware_regs/include/hardware/regs/otp_data.h
                COMMAND otp_header_parse ${PICO_SDK_PATH}/src/rp2350/hardware_regs/include/hardware/regs/otp_data.h ${GENERATED_H}
                )
    elseif(MSVC)
        set(GENERATED_JSON ${CMAKE_CURRENT_BINARY_DIR}/rp2350_otp_contents.json)
        add_custom_target(generate_otp_header DEPENDS ${GENERATED_JSON})
        add_custom_command(OUTPUT ${GENERATED_JSON}
                COMMENT "Generating ${GENERATED_JSON}"
                DEPENDS ${PICO_SDK_PATH}/src/rp2350/hardware_regs/include/hardware/regs/otp_data.h
                COMMAND otp_header_parse ${PICO_SDK_PATH}/src/rp2350/hardware_regs/include/hardware/regs/otp_data.h ${GENERATED_JSON}
                )
        # Cannot include json in the binary, as the string is too long, so needs to use pre-generated xxd output
        configure_file(${CMAKE_CURRENT_LIST_DIR}/otp_header_parser/rp2350.json.h ${CMAKE_CURRENT_BINARY_DIR}/rp2350.json.h COPYONLY)
    else()
        set(GENERATED_JSON ${CMAKE_CURRENT_BINARY_DIR}/rp2350_otp_contents.json)
        add_custom_target(generate_otp_header DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/rp2350.json.h)
        add_custom_command(OUTPUT ${GENERATED_JSON}
                COMMENT "Generating ${GENERATED_JSON}"
                DEPENDS ${PICO_SDK_PATH}/src/rp2350/hardware_regs/include/hardware/regs/otp_data.h
                COMMAND otp_header_parse ${PICO_SDK_PATH}/src/rp2350/hardware_regs/include/hardware/regs/otp_data.h ${GENERATED_JSON}
                )
        add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/rp2350.json.h
                COMMAND ${CMAKE_COMMAND}
                    -D GENERATED_JSON=${GENERATED_JSON}
                    -P ${CMAKE_CURRENT_LIST_DIR}/cmake/jsonh.cmake
                DEPENDS ${GENERATED_JSON}
                COMMENT "Configuring rp2350.json.h"
                VERBATIM)
              
    endif()
endif()

if (TARGET mbedtls)
    add_custom_target(embedded_data_no_libusb DEPENDS
            ${CMAKE_CURRENT_BINARY_DIR}/enc_bootloader_elf.h
            ${CMAKE_CURRENT_BINARY_DIR}/enc_bootloader_mbedtls_elf.h)
else()
    add_custom_target(embedded_data_no_libusb DEPENDS
            ${CMAKE_CURRENT_BINARY_DIR}/enc_bootloader_elf.h)
endif()

add_custom_target(embedded_data DEPENDS
        ${CMAKE_CURRENT_BINARY_DIR}/xip_ram_perms_elf.h
        ${CMAKE_CURRENT_BINARY_DIR}/flash_id_bin.h)
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xip_ram_perms_elf.h
        COMMAND ${CMAKE_COMMAND}
            -D BINARY_FILE=${XIP_RAM_PERMS_ELF}
            -D OUTPUT_NAME=xip_ram_perms_elf
            -P ${CMAKE_CURRENT_LIST_DIR}/cmake/binh.cmake
        DEPENDS xip_ram_perms
        COMMENT "Configuring xip_ram_perms_elf.h"
        VERBATIM)
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/enc_bootloader_elf.h
        COMMAND ${CMAKE_COMMAND}
            -D BINARY_FILE=${ENC_BOOTLOADER_ELF}
            -D OUTPUT_NAME=enc_bootloader_elf
            -P ${CMAKE_CURRENT_LIST_DIR}/cmake/binh.cmake
        DEPENDS enc_bootloader
        COMMENT "Configuring enc_bootloader_elf.h"
        VERBATIM)
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/enc_bootloader_mbedtls_elf.h
        COMMAND ${CMAKE_COMMAND}
            -D BINARY_FILE=${ENC_BOOTLOADER_MBEDTLS_ELF}
            -D OUTPUT_NAME=enc_bootloader_mbedtls_elf
            -P ${CMAKE_CURRENT_LIST_DIR}/cmake/binh.cmake
        DEPENDS enc_bootloader_mbedtls
        COMMENT "Configuring enc_bootloader_mbedtls_elf.h"
        VERBATIM)
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/flash_id_bin.h
        COMMAND ${CMAKE_COMMAND}
            -D BINARY_FILE=${FLASH_ID_BIN}
            -D OUTPUT_NAME=flash_id_bin
            -P ${CMAKE_CURRENT_LIST_DIR}/cmake/binh.cmake
        DEPENDS flash_id
        COMMENT "Configuring flash_id_bin.h"
        VERBATIM)

add_subdirectory(model)
add_subdirectory(errors)

add_subdirectory(picoboot_connection)
add_subdirectory(elf)
add_subdirectory(elf2uf2)

add_subdirectory(bintool)

if (NOT PICOTOOL_NO_LIBUSB)
    find_package(LIBUSB)
    set(OTP_EXE otp.cpp)
else()
    set(OTP_EXE no_otp.cpp)
endif()

add_subdirectory(${PICO_SDK_PATH}/src/common/pico_binary_info pico_binary_info)
add_subdirectory(${PICO_SDK_PATH}/src/common/boot_uf2_headers boot_uf2_headers)
add_subdirectory(${PICO_SDK_PATH}/src/common/boot_picoboot_headers boot_picoboot_headers)
add_subdirectory(${PICO_SDK_PATH}/src/common/boot_picobin_headers boot_picobin_headers)
add_subdirectory(${PICO_SDK_PATH}/src/common/pico_usb_reset_interface_headers pico_usb_reset_interface_headers)
add_subdirectory(${PICO_SDK_PATH}/src/rp2_common/boot_bootrom_headers boot_bootrom_headers)
add_subdirectory(${PICO_SDK_PATH}/src/host/pico_platform pico_platform)

add_library(regs_headers INTERFACE)
target_include_directories(regs_headers INTERFACE ${PICO_SDK_PATH}/src/rp2350/hardware_regs/include)

# Main picotool executable
add_executable(picotool
    data_locs.cpp
    get_enc_bootloader.cpp
    ${OTP_EXE}
    main.cpp)
add_dependencies(picotool embedded_data_no_libusb)
if (NOT PICOTOOL_NO_LIBUSB)
    target_sources(picotool PRIVATE get_xip_ram_perms.cpp)
    add_dependencies(picotool generate_otp_header embedded_data)
endif()
set(PROJECT_VERSION 2.2.0-a4)
set(PICOTOOL_VERSION 2.2.0-a4)
set(SYSTEM_VERSION "${CMAKE_SYSTEM_NAME}")
set(COMPILER_INFO "${CMAKE_C_COMPILER_ID}-${CMAKE_C_COMPILER_VERSION}, ${CMAKE_BUILD_TYPE}")
target_compile_definitions(picotool PRIVATE
        PICOTOOL_VERSION="${PICOTOOL_VERSION}"
        SYSTEM_VERSION="${SYSTEM_VERSION}"
        COMPILER_INFO="${COMPILER_INFO}"
        SUPPORT_RP2350_A2=1
        CODE_OTP=${PICOTOOL_CODE_OTP}
        )
# for OTP info
target_include_directories(picotool PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
# todo, this is a bit of an abstraction failure; but don't want to rev the SDK just for this right now
target_include_directories(picotool PRIVATE ${PICO_SDK_PATH}/src/rp2_common/pico_stdio_usb/include)
target_link_libraries(picotool
        pico_binary_info
        boot_uf2_headers
        boot_picoboot_headers
        boot_picobin_headers
        boot_bootrom_headers
        pico_platform_headers
        pico_usb_reset_interface_headers
        regs_headers
        model
        bintool
        elf2uf2
        errors
        nlohmann_json
        whereami)

if (NOT TARGET mbedtls)
    message("mbedtls not found - no signing/hashing support will be built")
    target_compile_definitions(picotool PRIVATE HAS_MBEDTLS=0)
else()
    target_compile_definitions(picotool PRIVATE HAS_MBEDTLS=1)
endif()

if (NOT LIBUSB_FOUND)
    if (PICOTOOL_NO_LIBUSB)
        message("PICOTOOL_NO_LIBUSB is set - no USB support will be built")
    else()
        message("libUSB is not found - no USB support will be built")
    endif()
    target_compile_definitions(picotool PRIVATE HAS_LIBUSB=0)
    target_link_libraries(picotool 
        picoboot_connection_header)
else()
    target_include_directories(picotool PRIVATE ${LIBUSB_INCLUDE_DIR})
    target_compile_definitions(picotool PRIVATE HAS_LIBUSB=1)
    target_link_libraries(picotool 
        picoboot_connection_cxx
        ${LIBUSB_LIBRARIES})
endif()

if (GENERATE_FIXED_DOCS_WIDTH)
    target_compile_definitions(picotool PRIVATE DOCS_WIDTH=140)
endif()

# allow `make install`
install(TARGETS picotool
    EXPORT picotool-targets
    RUNTIME DESTINATION ${INSTALL_BINDIR}
)

#Export the targets to a script
install(EXPORT picotool-targets
    FILE
        picotoolTargets.cmake
    DESTINATION
        ${INSTALL_CONFIGDIR}
)

#Create a ConfigVersion.cmake file
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
    ${CMAKE_CURRENT_BINARY_DIR}/picotoolConfigVersion.cmake
    VERSION ${PROJECT_VERSION}
    COMPATIBILITY SameMajorVersion
    ARCH_INDEPENDENT
)

configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/cmake/picotoolConfig.cmake
    ${CMAKE_CURRENT_BINARY_DIR}/picotoolConfig.cmake
    INSTALL_DESTINATION ${INSTALL_CONFIGDIR}
)

#Install the config and configversion
install(FILES
    ${CMAKE_CURRENT_BINARY_DIR}/picotoolConfig.cmake
    ${CMAKE_CURRENT_BINARY_DIR}/picotoolConfigVersion.cmake
    DESTINATION ${INSTALL_CONFIGDIR}
)

#Install enc_bootloader.elf
install(FILES
    ${ENC_BOOTLOADER_ELF}
    DESTINATION ${INSTALL_DATADIR}
)

if (TARGET mbedtls)
    #Install enc_bootloader_mbedtls.elf
    install(FILES
        ${ENC_BOOTLOADER_MBEDTLS_ELF}
        DESTINATION ${INSTALL_DATADIR}
        RENAME enc_bootloader_mbedtls.elf
    )
endif()

if (NOT PICOTOOL_NO_LIBUSB)
    if (NOT PICOTOOL_CODE_OTP)
        #Install the otp json
        install(FILES
            ${GENERATED_JSON}
            DESTINATION ${INSTALL_DATADIR}
        )
    endif()

    #Install xip_ram_perms.elf
    install(FILES
        ${XIP_RAM_PERMS_ELF}
        DESTINATION ${INSTALL_DATADIR}
    )
endif()
